package execution;

import java.awt.Desktop;
import java.awt.Dimension;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.HierarchyBoundsListener;
import java.awt.event.HierarchyEvent;
import java.beans.PropertyVetoException;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;

import javax.swing.JFileChooser;
import javax.swing.JInternalFrame;
import javax.swing.JOptionPane;

import recordsDatabase.Category;
import recordsDatabase.CategoryDetails;
import recordsDatabase.ListOfCategories;
import recordsDatabase.ListOfPersons;
import recordsDatabase.Person;

import resources.Logger;
import resultsDatabase.Field;
import resultsDatabase.ListOfResults;
import threads.MemoryUsageLogger;
import threads.MineThread;
import threads.PDFLoaderThread;

import gui.WindowMain;
import gui.WindowSelectFieldsToMine;

import gui.WindowAbout;
import gui.WindowCategories;
import gui.WindowRecords;
import gui.WindowMineResults;
import gui.WindowSettings;

public class Manager {
	// keep an instance of each window available
	// private WindowSettings windowSettings;
	private WindowCategories windowCategories;
	private WindowRecords windowRecords;
	private ArrayList<WindowRecords> windowPersonsList = new ArrayList<WindowRecords>();
	private WindowSelectFieldsToMine windowSelectFieldsToMine;
	private WindowMineResults windowMineResults;
	private WindowMain mainWindow;
	private WindowSettings settingsWindow;
	private MineThread miner;
	private Manager manager = this;
	private int totalSetsToMine;
	private int setsMined;
	private MemoryUsageLogger memLog = new MemoryUsageLogger();
	private String loggerFileName = "log.txt";

	public Manager() {
		// initial memory usage thread
		memLog.start();

		// create the main gui
		mainWindow = new WindowMain(this);
		mainWindow.setSize(1000, 600);
		mainWindow.setLocation(0, 0);
		mainWindow.setVisible(true);
		mainWindow.getContentPane().addHierarchyBoundsListener(
				new HierarchyBoundsListener() {
					public void ancestorMoved(HierarchyEvent e) {
						if (resources.Settings.getInstance()
								.getTileOpenWindowsResize()) {
							checkDesktopPaneSize();
							tileInternalFrames();
						}
					}

					public void ancestorResized(HierarchyEvent e) {
						if (resources.Settings.getInstance()
								.getTileOpenWindowsResize()) {
							checkDesktopPaneSize();
							tileInternalFrames();
						}
					}
				});
		// set the menu's state to new project
		mainWindow.menuNewProjectState();
		mainWindow.progressBar.setVisible(false);

	}

	public void startMonitoring() {
		// add the event listeners which will take action each time a menu item
		// is selected
		addEvents();
		mainWindow.addStatusMessage("DataMiner loaded succesfully!");
		// the manager needs to do nothing else than just wait for the user to
		// select a menu item
		// and take appropriate action
	}

	private void addEvents() {
		// Event listeners
		// file menu
		mainWindow.menuNewProjectActionPerformed(new NewProject());
		mainWindow.menuSettingsActionPerformed(new Settings());
		mainWindow.menuQuitActionPerformed(new Quit());
		// load menu
		mainWindow.menuPDFActionPerformed(new LoadPDF());
		// database menu
		mainWindow.menuCategoriesActionPerformed(new ViewCategories());
		mainWindow.menuRecordsActionPerformed(new ViewRecords());
		// mine menu
		mainWindow.menuSelectFieldsToMineActionPerformed(new FieldsToMine());
		mainWindow.menuViewResultsActionPerformed(new ShowResults());
		// help menu
		mainWindow.menuDocumentationActionPerformed(new Documentation());
		mainWindow.menuAboutDataMinerActionPerformed(new AboutDataMiner());

	}

	private void reset() {
		mainWindow.addStatusMessage("Destroying existing databases.");
		mainWindow.progressBar.setVisible(true);
		mainWindow.progressBar.setMaximum(10);
		mainWindow.progressBar.setOpaque(true);
		mainWindow.progressBar.setValue(1);
		ListOfPersons.getInstance().reset();
		mainWindow.progressBar.setValue(2);
		// call the garbage collector
		System.gc();
		mainWindow.progressBar.setValue(3);
		ListOfCategories.getInstance().reset();
		mainWindow.progressBar.setValue(4);
		// call the garbage collector
		System.gc();
		mainWindow.progressBar.setValue(5);
		ListOfResults.getInstance().reset();
		mainWindow.progressBar.setValue(6);
		// call the garbage collector
		System.gc();
		mainWindow.progressBar.setValue(7);
		mainWindow.addStatusMessage("Existing databases destroyed.");
		mainWindow.addStatusMessage("Closing all open windows.");
		if (settingsWindow != null) {
			settingsWindow.dispose();
			mainWindow.desktopPane1.remove(settingsWindow);
		}
		if (windowCategories != null) {
			windowCategories.dispose();
			mainWindow.desktopPane1.remove(windowCategories);
		}
		if (windowRecords != null) {
			windowRecords.dispose();
			mainWindow.desktopPane1.remove(windowRecords);
		}
		if (windowSelectFieldsToMine != null)
			windowSelectFieldsToMine.dispose();
		if (windowMineResults != null)
			windowMineResults.dispose();
		for (WindowRecords w : windowPersonsList) {
			if (w != null) {
				w.dispose();
				mainWindow.desktopPane1.remove(w);
			}
		}
		mainWindow.progressBar.setValue(8);
		// call the garbage collector
		System.gc();
		mainWindow.progressBar.setValue(9);
		mainWindow.addStatusMessage("Open windows closed");
		mainWindow.progressBar.setValue(10);
		mainWindow.progressBar.setVisible(false);
	}

	public synchronized void updateTotalSetsToMine(int value) {
		totalSetsToMine = value;
	}

	public synchronized void updateSetsMined() {
		setsMined++;
		if (windowMineResults != null) {
			windowMineResults.updateMineStatus(totalSetsToMine, setsMined);
		}
	}

	public void quit() {
		memLog.stopThread();
		// write log
		Logger.getInstance().writeToFile(loggerFileName);
		System.exit(0);
	}

	public void createPersonWindow(Person p) {
		WindowRecords windowPerson = new WindowRecords(p);
		windowPerson.setVisible(true);
		windowPerson.setEnabled(true);
		windowPerson.setResizable(true);
		int height = (int) Math.round(mainWindow.desktopPane1.getSize()
				.getHeight());
		int width = (int) Math.round(mainWindow.desktopPane1.getSize()
				.getWidth());
		windowPerson.setSize(width, height);
		mainWindow.desktopPane1.add(windowPerson);
		windowPersonsList.add(windowPerson);
		tileInternalFrames();
	}

	class NewProject implements ActionListener {
		public void actionPerformed(ActionEvent e) {
			reset();
			// call the garbage collector
			System.gc();
			mainWindow.addStatusMessage("New Project Started!");
			mainWindow.menuNewProjectState();
		}
	}

	class LoadPDF implements ActionListener {
		public void actionPerformed(ActionEvent e) {
			mainWindow.menuLoadingDBState();
			mainWindow.addStatusMessage("Waiting to select a pdf file");
			// create a JFileChooser with its appropriate properties
			JFileChooser fileChooser = new JFileChooser();
			fileChooser
					.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
			int fileVal = fileChooser.showOpenDialog(mainWindow.menuItemPDF
					.getComponent());
			// check if the cancel button was pressed
			if (fileVal == JFileChooser.CANCEL_OPTION) {
				mainWindow.menuNewProjectState();
				mainWindow
						.addStatusMessage("Load a pdf file has been cancel by the user");
			} else {
				// if everything is ok then get the selected file from the user
				File fileName = fileChooser.getSelectedFile();
				// if the file is empty display an error message
				if (fileName == null || fileName.getName().equals("")
						|| !fileName.getName().contains(".pdf")) {
					JOptionPane.showMessageDialog(mainWindow.menuItemPDF
							.getComponent(),
							"Please enter a correct pdf filename.", "Error",
							JOptionPane.ERROR_MESSAGE);
					mainWindow.menuNewProjectState();
					mainWindow
							.addStatusMessage("Load a pdf file has been cancel due to incorrect filename");
				} else
				// if the path name is OK, call the loader!
				{
					PDFLoaderThread pdfLoaderThread = new PDFLoaderThread(
							mainWindow, fileName.getPath());
					pdfLoaderThread.start();
				}
			}
		}
	}

	class ViewRecords implements ActionListener {

		public void actionPerformed(ActionEvent e) {
			if (!resources.Settings.getInstance().getOpenWindows()) {
				if (multipleOpenWindows()) {
					closeOpenWindows();
				}
			}
			if (windowRecords != null) {
				windowRecords.dispose();
				mainWindow.desktopPane1.remove(windowRecords);
			}
			windowRecords = new WindowRecords();
			windowRecords.setVisible(true);
			windowRecords.setEnabled(true);
			windowRecords.setResizable(true);
			int height = (int) Math.round(mainWindow.desktopPane1.getSize()
					.getHeight());
			int width = (int) Math.round(mainWindow.desktopPane1.getSize()
					.getWidth());
			windowRecords.setSize(width, height);
			try {
				windowRecords.setSelected(true);
			} catch (PropertyVetoException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}
			mainWindow.desktopPane1.add(windowRecords);
			tileInternalFrames();
		}
	}

	class ViewCategories implements ActionListener {
		public void actionPerformed(ActionEvent e) {
			if (!resources.Settings.getInstance().getOpenWindows()) {
				if (multipleOpenWindows()) {
					closeOpenWindows();
				}
			}
			if (windowCategories != null) {
				windowCategories.dispose();
				mainWindow.desktopPane1.remove(windowCategories);
			}
			windowCategories = new WindowCategories();
			windowCategories.setVisible(true);
			windowCategories.setEnabled(true);
			windowCategories.setResizable(true);
			int height = (int) Math.round(mainWindow.desktopPane1.getSize()
					.getHeight());
			int width = (int) Math.round(mainWindow.desktopPane1.getSize()
					.getWidth());
			windowCategories.setSize(width, height);
			mainWindow.desktopPane1.add(windowCategories);
			tileInternalFrames();
		}
	}

	class FieldsToMine implements ActionListener {
		public void actionPerformed(ActionEvent e) {
			if (!resources.Settings.getInstance().getOpenWindows()) {
				if (multipleOpenWindows()) {
					closeOpenWindows();
				}
			}
			if (windowSelectFieldsToMine != null) {
				windowSelectFieldsToMine.dispose();
				mainWindow.desktopPane1.remove(windowSelectFieldsToMine);

			}
			// create the window
			windowSelectFieldsToMine = new WindowSelectFieldsToMine();
			// populate the tabs
			for (Category c : ListOfCategories.getInstance()
					.getCategoriesList()) {
				ArrayList<String> values = new ArrayList<String>();
				for (CategoryDetails cd : c.getEntriesList()) {
					values.add(cd.getField());
				}
				windowSelectFieldsToMine.addTab(c.getCategoryName(), values);
			}
			// set the window visible
			windowSelectFieldsToMine.setVisible(true);
			windowSelectFieldsToMine.setEnabled(true);
			mainWindow.desktopPane1.add(windowSelectFieldsToMine);
			// enable the event handlers for the mine window
			// addMineEvents
			windowSelectFieldsToMine.buttonMineActionPerformed(new Miner());
			tileInternalFrames();
		}
	}

	class ShowResults implements ActionListener {
		public void actionPerformed(ActionEvent e) {
			if (!resources.Settings.getInstance().getOpenWindows()) {
				if (multipleOpenWindows()) {
					closeOpenWindows();
				}
			}
			if (miner != null
					|| !ListOfResults.getInstance().getResultsList().isEmpty()) {
				// create the results window
				if (windowMineResults != null) {
					windowMineResults.dispose();
					mainWindow.desktopPane1.remove(windowMineResults);
				}
				windowMineResults = new WindowMineResults(manager);
				windowMineResults.setVisible(true);
				windowMineResults.setEnabled(true);
				windowMineResults.setResizable(true);
				int height = (int) Math.round(mainWindow.desktopPane1.getSize()
						.getHeight());
				int width = (int) Math.round(mainWindow.desktopPane1.getSize()
						.getWidth());
				windowMineResults.setSize(width, height);
				mainWindow.desktopPane1.add(windowMineResults);
				tileInternalFrames();
				if (miner.isAlive()) {
					windowMineResults.setMiner(miner);
					if (miner.areThreadsPaused())
						windowMineResults.pausedState();
				} else {
					windowMineResults.disableStopMiningButton();
					windowMineResults.disablePauseMiningButton();
				}
				tileInternalFrames();
			}
		}
	}

	class Quit implements ActionListener {
		public void actionPerformed(ActionEvent e) {
			memLog.stopThread();
			// write log
			Logger.getInstance().writeToFile(loggerFileName);
			System.exit(0);
		}
	}

	class Miner implements ActionListener {
		public void actionPerformed(ActionEvent e) {
			totalSetsToMine = 0;
			setsMined = 0;
			mainWindow.resetStatus();
			ArrayList<Field> values = new ArrayList<Field>();
			// enable the view results window
			mainWindow.menuViewResultsActionPerformed(new ShowResults());
			// grab the selected fields to mine
			// and close the selectFiledsToMine window
			if (windowSelectFieldsToMine != null) {
				values = windowSelectFieldsToMine.getSelectedFields();
				windowSelectFieldsToMine.dispose();
				mainWindow.desktopPane1.remove(windowSelectFieldsToMine);

			}
			// create the results window
			if (windowMineResults != null) {
				ListOfResults.getInstance().reset();
				windowMineResults.dispose();
				mainWindow.desktopPane1.remove(windowMineResults);
			}
			windowMineResults = new WindowMineResults(manager);
			windowMineResults.setVisible(true);
			windowMineResults.setEnabled(true);
			windowMineResults.setResizable(true);
			int height = (int) Math.round(mainWindow.desktopPane1.getSize()
					.getHeight());
			int width = (int) Math.round(mainWindow.desktopPane1.getSize()
					.getWidth());
			windowMineResults.setSize(width, height);
			mainWindow.desktopPane1.add(windowMineResults);
			tileInternalFrames();
			// call the miner to start mining
			miner = new MineThread(values, mainWindow, windowMineResults,
					manager);
			miner.start();
			windowMineResults.setMiner(miner);
		}
	}

	class Settings implements ActionListener {
		public void actionPerformed(ActionEvent e) {
			if (!resources.Settings.getInstance().getOpenWindows()) {
				if (multipleOpenWindows()) {
					closeOpenWindows();
				}
			}
			if (settingsWindow != null) {
				settingsWindow.dispose();
				mainWindow.desktopPane1.remove(settingsWindow);
			}
			settingsWindow = new WindowSettings();
			settingsWindow.setVisible(true);
			settingsWindow.setEnabled(true);
			settingsWindow.setResizable(true);
			int height = (int) Math.round(mainWindow.desktopPane1.getSize()
					.getHeight());
			int width = (int) Math.round(mainWindow.desktopPane1.getSize()
					.getWidth());
			settingsWindow.setSize(width, height);
			mainWindow.desktopPane1.add(settingsWindow);
			tileInternalFrames();
		}
	}

	class AboutDataMiner implements ActionListener {
		public void actionPerformed(ActionEvent e) {
			WindowAbout windowAbout = new WindowAbout();
			windowAbout.setVisible(true);
			windowAbout.setFocusable(true);
		}
	}

	class Documentation implements ActionListener {
		public void actionPerformed(ActionEvent e) {
			File document = new File("dataMinerManual.pdf");
			Desktop dt = Desktop.getDesktop();
			try {
				dt.open(document);
			} catch (IOException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}
		}
	}

	private void tileInternalFrames() {
		if (resources.Settings.getInstance().getTileOpenWindows()) {
			// How many frames do we have?
			JInternalFrame[] allframes = mainWindow.desktopPane1.getAllFrames();
			int count = allframes.length;
			if (count == 0)
				return;
			// Determine the necessary grid size
			int sqrt = (int) Math.sqrt(count);
			int rows = sqrt;
			int cols = sqrt;
			if (rows * cols < count) {
				cols++;
				if (rows * cols < count) {
					rows++;
				}
			}
			// Define some initial values for size & location.
			Dimension size = mainWindow.desktopPane1.getSize();
			int w = size.width / cols;
			int h = size.height / rows;
			int x = 0;
			int y = 0;
			// Iterate over the frames, deiconifying any iconified frames and
			// then
			// relocating & resizing each.
			for (int i = 0; i < rows; i++) {
				for (int j = 0; j < cols && ((i * cols) + j < count); j++) {
					JInternalFrame f = allframes[(i * cols) + j];
					if (!f.isClosed() && f.isIcon()) {
						try {
							f.setIcon(false);
						} catch (PropertyVetoException ignored) {
						}
					}
					mainWindow.desktopPane1.getDesktopManager().resizeFrame(f,
							x, y, w, h);
					x += w;
				}
				y += h; // start the next row
				x = 0;
			}
		}
	}

	private boolean multipleOpenWindows() {
		if (windowCategories != null)
			return true;
		else if (windowRecords != null)
			return true;
		else if (windowSelectFieldsToMine != null)
			return true;
		else if (windowMineResults != null)
			return true;
		else if (settingsWindow != null)
			return true;
		else
			return false;
	}

	private void closeOpenWindows() {
		if (windowCategories != null) {
			windowCategories.dispose();
			mainWindow.desktopPane1.remove(windowCategories);
		}
		if (windowRecords != null) {
			windowRecords.dispose();
			mainWindow.desktopPane1.remove(windowRecords);
		}
		if (windowSelectFieldsToMine != null) {
			windowSelectFieldsToMine.dispose();
			mainWindow.desktopPane1.remove(windowSelectFieldsToMine);
		}
		if (windowMineResults != null) {
			windowMineResults.dispose();
			mainWindow.desktopPane1.remove(windowMineResults);
		}
		if (settingsWindow != null) {
			settingsWindow.dispose();
			mainWindow.desktopPane1.remove(settingsWindow);
		}
	}

	private void checkDesktopPaneSize() {
		if ((mainWindow.desktopPane1.getSize().height > mainWindow.getSize().height)
				|| (mainWindow.desktopPane1.getSize().width > mainWindow
						.getSize().width)) {
			mainWindow.desktopPane1.setSize(0, 0);
		}
	}

}
